using System.Collections.Generic;
using System.Runtime.CompilerServices;
using org.neo4j.graphdb;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.xa
{

	using XAResource = javax.transaction.xa.XAResource;

	using PropertyIndex = org.neo4j.kernel.impl.core.PropertyIndex;
	using ReadOnlyNeoException = org.neo4j.kernel.impl.core.ReadOnlyNeoException;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using PropertyStore = org.neo4j.kernel.impl.nioneo.store.PropertyStore;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using RelationshipTypeStore = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
	using PersistenceSource = org.neo4j.kernel.impl.persistence.PersistenceSource;
	using ResourceConnection = org.neo4j.kernel.impl.persistence.ResourceConnection;
	using XaDataSourceManager = org.neo4j.kernel.impl.transaction.XaDataSourceManager;
	using XaDataSource = org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

///
/// <summary> * The NioNeo persistence source implementation. If this class is registered as
/// * persistence source for Neo4j kernel operations that are performed on the node space
/// * will be forwarded to this class <seealso cref="ResourceConnection"/> implementation. </summary>
/// 
	public class NioNeoDbPersistenceSource : PersistenceSource
	{
		private const string MODULE_NAME = "NioNeoDbPersistenceSource";

		private NeoStoreXaDataSource xaDs = null;
		private string dataSourceName = null;
		private ResourceConnection readOnlyResourceConnection;

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void init()
		{
		// Do nothing
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void start(XaDataSourceManager xaDsManager)
		{
			xaDs = (NeoStoreXaDataSource) xaDsManager.getXaDataSource("nioneodb");
			if (xaDs == null)
			{
				throw new IllegalStateException("Unable to get nioneodb datasource");
			}
			readOnlyResourceConnection = new ReadOnlyResourceConnection(xaDs);
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void reload()
		{
		// Do nothing
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void stop()
		{
			if (xaDs != null)
			{
				xaDs.Close();
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void destroy()
		{
		// Do nothing
		}

		public virtual string getModuleName()
		{
			return MODULE_NAME;
		}

		public virtual ResourceConnection createResourceConnection()
		{
			if (xaDs.isReadOnly())
			{
				throw new ReadOnlyNeoException();
			}
			return new NioNeoDbResourceConnection(this.xaDs);
		}

		public virtual ResourceConnection createReadOnlyResourceConnection()
		{
			return readOnlyResourceConnection;
		}

		private class ReadOnlyResourceConnection : ResourceConnection
		{
			private readonly NeoReadTransaction neoTransaction;
			private readonly RelationshipTypeStore relTypeStore;

			internal ReadOnlyResourceConnection(NeoStoreXaDataSource xaDs)
			{
				this.neoTransaction = xaDs.getReadOnlyTransaction();
				this.relTypeStore = xaDs.getNeoStore().getRelationshipTypeStore();
			}

			public virtual XAResource getXAResource()
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void destroy()
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void nodeDelete(int nodeId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual int nodeAddProperty(int nodeId, PropertyIndex index, object @value)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void nodeChangeProperty(int nodeId, int propertyId, object @value)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void nodeRemoveProperty(int nodeId, int propertyId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void nodeCreate(int nodeId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void relationshipCreate(int id, int typeId, int startNodeId, int endNodeId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void relDelete(int relId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual int relAddProperty(int relId, PropertyIndex index, object @value)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void relChangeProperty(int relId, int propertyId, object @value)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void relRemoveProperty(int relId, int propertyId)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual string loadIndex(int id)
			{
				return neoTransaction.getPropertyIndex(id);
			}

			public virtual PropertyIndexData[] loadPropertyIndexes(int maxCount)
			{
				return neoTransaction.getPropertyIndexes(maxCount);
			}

			public virtual object loadPropertyValue(int id)
			{
				return neoTransaction.propertyGetValue(id);
			}

			public virtual RelationshipTypeData[] loadRelationshipTypes()
			{
				RelationshipTypeData[] relTypeData = relTypeStore.getRelationshipTypes();
				RelationshipTypeData[] rawRelTypeData = new RelationshipTypeData[relTypeData.Length];
				for (int i = 0; i < relTypeData.Length; i++)
				{
					rawRelTypeData[i] = new RelationshipTypeData(relTypeData[i].getId(), relTypeData[i].Name);
				}
				return rawRelTypeData;
			}

			public virtual bool nodeLoadLight(int id)
			{
				return neoTransaction.nodeLoadLight(id);
			}

			public virtual ArrayMap<int, PropertyData> nodeLoadProperties(int nodeId)
			{
				return neoTransaction.nodeGetProperties(nodeId);
			}

			public virtual RelationshipData relLoadLight(int id)
			{
				return neoTransaction.relationshipLoad(id);
			}

			public virtual ArrayMap<int, PropertyData> relLoadProperties(int relId)
			{
				return neoTransaction.relGetProperties(relId);
			}

			public virtual void createPropertyIndex(string key, int id)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual void createRelationshipType(int id, string name)
			{
				throw new IllegalStateException("This is a read only transaction, " + "this method should never be invoked");
			}

			public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
			{
				return neoTransaction.getRelationshipChainPosition(nodeId);
			}

            public virtual IEnumerable<RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
			{
				return neoTransaction.getMoreRelationships(nodeId, position);
			}
		}

		private class NioNeoDbResourceConnection : ResourceConnection
		{
			private NeoStoreXaConnection xaCon;
			private NodeEventConsumer nodeConsumer;
			private RelationshipEventConsumer relConsumer;
			private RelationshipTypeEventConsumer relTypeConsumer;
			private PropertyIndexEventConsumer propIndexConsumer;
			private PropertyStore propStore;

			internal NioNeoDbResourceConnection(NeoStoreXaDataSource xaDs)
			{
				this.xaCon = (NeoStoreXaConnection) xaDs.getXaConnection();
				nodeConsumer = xaCon.getNodeConsumer();
				relConsumer = xaCon.getRelationshipConsumer();
				relTypeConsumer = xaCon.getRelationshipTypeConsumer();
				propIndexConsumer = xaCon.getPropertyIndexConsumer();
				propStore = xaCon.getPropertyStore();
			}

			public virtual XAResource getXAResource()
			{
				return this.xaCon.getXaResource();
			}

			public virtual void destroy()
			{
				xaCon.destroy();
				xaCon = null;
				nodeConsumer = null;
				relConsumer = null;
				relTypeConsumer = null;
				propIndexConsumer = null;
			}

			public virtual void nodeDelete(int nodeId)
			{
				nodeConsumer.deleteNode(nodeId);
			}

			public virtual int nodeAddProperty(int nodeId, PropertyIndex index, object @value)
			{
				int propertyId = propStore.nextId();
				nodeConsumer.addProperty(nodeId, propertyId, index, @value);
				return propertyId;
			}

			public virtual void nodeChangeProperty(int nodeId, int propertyId, object @value)
			{
				nodeConsumer.changeProperty(nodeId, propertyId, @value);
			}

			public virtual void nodeRemoveProperty(int nodeId, int propertyId)
			{
				nodeConsumer.removeProperty(nodeId, propertyId);
			}

			public virtual void nodeCreate(int nodeId)
			{
				nodeConsumer.createNode(nodeId);
			}

			public virtual void relationshipCreate(int id, int typeId, int startNodeId, int endNodeId)
			{
				relConsumer.createRelationship(id, startNodeId, endNodeId, typeId);
			}

			public virtual void relDelete(int relId)
			{
				relConsumer.deleteRelationship(relId);
			}

			public virtual int relAddProperty(int relId, PropertyIndex index, object @value)
			{
				int propertyId = propStore.nextId();
				relConsumer.addProperty(relId, propertyId, index, @value);
				return propertyId;
			}

			public virtual void relChangeProperty(int relId, int propertyId, object @value)
			{
				relConsumer.changeProperty(relId, propertyId, @value);
			}

			public virtual void relRemoveProperty(int relId, int propertyId)
			{
				relConsumer.removeProperty(relId, propertyId);
			}

			public virtual string loadIndex(int id)
			{
				return propIndexConsumer.getKeyFor(id);
			}

			public virtual PropertyIndexData[] loadPropertyIndexes(int maxCount)
			{
				return propIndexConsumer.getPropertyIndexes(maxCount);
			}

			public virtual object loadPropertyValue(int id)
			{
				return xaCon.getNeoTransaction().propertyGetValue(id);
			}

			public virtual RelationshipTypeData[] loadRelationshipTypes()
			{
				RelationshipTypeData[] relTypeData = relTypeConsumer.getRelationshipTypes();
				RelationshipTypeData[] rawRelTypeData = new RelationshipTypeData[relTypeData.Length];
				for (int i = 0; i < relTypeData.Length; i++)
				{
					rawRelTypeData[i] = new RelationshipTypeData(relTypeData[i].getId(), relTypeData[i].Name);
				}
				return rawRelTypeData;
			}

			public virtual bool nodeLoadLight(int id)
			{
				return nodeConsumer.loadLightNode(id);
			}

			public virtual ArrayMap<int, PropertyData> nodeLoadProperties(int nodeId)
			{
				return nodeConsumer.getProperties(nodeId);
			}

			public virtual RelationshipData relLoadLight(int id)
			{
				return relConsumer.getRelationship(id);
			}

			public virtual ArrayMap<int, PropertyData> relLoadProperties(int relId)
			{
				return relConsumer.getProperties(relId);
			}

			public virtual void createPropertyIndex(string key, int id)
			{
				propIndexConsumer.createPropertyIndex(id, key);
			}

			public virtual void createRelationshipType(int id, string name)
			{
				relTypeConsumer.addRelationshipType(id, name);
			}

			public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
			{
				return relConsumer.getRelationshipChainPosition(nodeId);
			}

            public virtual IEnumerable<RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
			{
				return relConsumer.getMoreRelationships(nodeId, position);
			}
		}

		public override string ToString()
		{
			return "A persistence source to [" + dataSourceName + "]";
		}

		public virtual int nextId<T1>(System.Type<T1> clazz)
		{
			return xaDs.nextId(clazz);
		}

	// for recovery, returns a xa
		public virtual XAResource getXaResource()
		{
			return this.xaDs.getXaConnection().getXaResource();
		}

		public virtual void setDataSourceName(string dataSourceName)
		{
			this.dataSourceName = dataSourceName;
		}

		public virtual string getDataSourceName()
		{
			return this.dataSourceName;
		}

		public virtual long getHighestPossibleIdInUse<T1>(System.Type<T1> clazz)
		{
			return xaDs.getHighestPossibleIdInUse(clazz);
		}

		public virtual long getNumberOfIdsInUse<T1>(System.Type<T1> clazz)
		{
			return xaDs.getNumberOfIdsInUse(clazz);
		}

		public virtual XaDataSource getXaDataSource()
		{
			return xaDs;
		}
	}
}